<template>
  <div class="minio-upload">
    <!-- 上传按钮 -->
    <el-button
      :type="buttonType"
      :plain="plain"
      :icon="icon"
      :size="size"
      @click="handleUploadClick"
      :disabled="disabled"
      :loading="uploading"
      class="upload-trigger"
    >
      {{ buttonText }}
    </el-button>

    <!-- 文件上传对话框 -->
    <el-dialog
      v-if="dialogVisible"
      :title="dialogTitle"
      :visible.sync="dialogVisible"
      :width="dialogWidth"
      append-to-body
      :close-on-click-modal="false"
      @close="handleDialogClose"
    >
      <!-- 分片上传进度条 -->
      <el-progress
        v-if="showProgress"
        :percentage="uploadPercentage"
        :stroke-width="8"
        style="margin-bottom: 15px;"
      ></el-progress>

      <el-upload
        class="upload-demo"
        ref="upload"
        action="#"
        :auto-upload="false"
        :on-change="handleFileChange"
        :file-list="fileList"
        :limit="1"
        :accept="currentAccept"
        :on-exceed="handleExceed"
        :before-remove="beforeRemove"
      >
        <el-button size="small" type="primary">点击选择文件</el-button>
        <div slot="tip" class="el-upload__tip custom-upload-tip">
          <span class="required-star">*</span>
          支持上传 {{ formatAcceptText(currentAccept) }} 格式文件，
          单个文件大小不超过 {{ fileSizeLimit }}MB，仅支持上传 1 个文件
          <span v-if="fileSizeLimit > 10" style="color:#f56c6c;">
            （超过10MB将自动启用分片上传）
          </span>
        </div>
      </el-upload>

      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button
          type="primary"
          @click="submitUpload"
          :disabled="!canUpload"
        >
          上 传
        </el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { uploadFile } from '@/api/upload/minioFile'
import {
  initMultipartUpload,
  completeMultipartUpload,
  listUploadedParts,
  abortMultipartUpload,
  uploadPartByServer  // 新增：用于服务端中转上传分片的接口
} from '@/api/upload/minioMultipart'
import { ACCEPT_CONFIG } from '@/components/MinioFileUpload/config/accept'
import SparkMD5 from 'spark-md5'

export default {
  name: 'MinioFileUpload',
  props: {
    // 按钮文本
    buttonText: {
      type: String,
      default: '上传文件'
    },
    // 按钮类型
    buttonType: {
      type: String,
      default: 'primary'
    },
    // 是否朴素按钮
    plain: {
      type: Boolean,
      default: true
    },
    // 按钮图标
    icon: {
      type: String,
      default: 'el-icon-upload'
    },
    // 按钮大小
    size: {
      type: String,
      default: 'mini'
    },
    // 是否禁用
    disabled: {
      type: Boolean,
      default: false
    },
    // 对话框标题
    dialogTitle: {
      type: String,
      default: '文件上传'
    },
    // 对话框宽度
    dialogWidth: {
      type: String,
      default: '500px'
    },
    // 指定文件类型组key
    fileTypeGroup: {
      type: String,
      default: 'all'
    },
    // 单个文件大小限制(MB)
    fileSizeLimit: {
      type: Number,
      default: 50
    },
    // 分片大小(MB) - 默认5MB，最小不能小于5MB(MinIO限制)
    chunkSize: {
      type: Number,
      default: 5
    }
  },
  data() {
    return {
      dialogVisible: false,
      fileList: [],
      selectedFile: null,
      uploading: false,
      currentAccept: '',
      acceptConfig: ACCEPT_CONFIG,
      // 分片上传相关参数
      uploadId: '',
      uploadPercentage: 0,
      showProgress: false,
      // 断点续传相关
      uploadedChunks: [],
      totalChunks: 0,
      fileMd5: '',
      // 分片MD5存储
      chunkMd5Map: {}
    }
  },
  created() {
    this.setAcceptByGroup(this.fileTypeGroup)
  },
  computed: {
    canUpload() {
      return !!this.selectedFile && !this.uploading
    },
    // 是否需要分片上传(超过10MB)
    needChunkUpload() {
      return this.selectedFile && this.selectedFile.size > 10 * 1024 * 1024
    },
    // 分片大小(字节)
    chunkSizeBytes() {
      // 确保分片大小不小于5MB(MinIO要求)
      return Math.max(this.chunkSize * 1024 * 1024, 5 * 1024 * 1024)
    }
  },
  methods: {
    /** 根据文件类型组设置接受的文件类型 */
    setAcceptByGroup(groupKey) {
      if (groupKey === 'all') {
        this.currentAccept = this.acceptConfig.getAll().join(',')
      } else if (this.acceptConfig[groupKey]) {
        this.currentAccept = this.acceptConfig[groupKey].join(',')
      } else {
        this.currentAccept = this.acceptConfig.getAll().join(',')
      }
    },

    /** 格式化文件类型文本显示 */
    formatAcceptText(accept) {
      if (!accept) return ''
      return accept.split(',')
        .map(type => type.replace(/^\./, '').toUpperCase())
        .join('、')
    },

    /** 重置文件选择 */
    resetFileSelection() {
      this.fileList = []
      this.selectedFile = null
      if (this.$refs.upload) {
        this.$refs.upload.clearFiles()
      }
      this.uploadedChunks = []
      this.totalChunks = 0
      this.fileMd5 = ''
      this.chunkMd5Map = {}
      this.uploadId = ''
      this.uploadPercentage = 0
      this.showProgress = false
    },

    /** 打开上传对话框 */
    handleUploadClick() {
      this.resetUploadState()
      this.dialogVisible = true
    },

    /** 处理文件选择变化 */
    handleFileChange(file, fileList) {
      // 检查文件大小
      if (file.size > this.fileSizeLimit * 1024 * 1024) {
        this.$message.error(`文件 ${file.name} 大小超过${this.fileSizeLimit}MB限制`)
        this.fileList = []
        this.selectedFile = null
        return
      }

      // 只保留最后选择的一个文件
      const lastFile = fileList.slice(-1)[0]
      this.fileList = lastFile ? [lastFile] : []
      this.selectedFile = lastFile ? lastFile.raw : null

      // 如果是大文件，提前计算MD5和分片MD5
      if (this.needChunkUpload && this.selectedFile) {
        this.calculateFileAndChunkMd5(this.selectedFile)
      }
    },

    /** 处理文件超出限制 */
    handleExceed(files, fileList) {
      this.$message.warning('每次只能上传一个文件，请先移除已选择的文件')
      const lastFile = fileList.slice(-1)[0]
      this.fileList = [lastFile]
      this.selectedFile = lastFile.raw

      // 如果是大文件，提前计算MD5
      if (this.needChunkUpload && this.selectedFile) {
        this.calculateFileAndChunkMd5(this.selectedFile)
      }
    },

    /** 移除文件前的回调 */
    beforeRemove(file, fileList) {
      return this.$confirm(`确定要移除 ${file.name} 吗？`)
    },

    /** 提交上传 */
    submitUpload() {
      if (!this.selectedFile) {
        this.$message.error("请选择文件")
        return
      }

      // 根据文件大小选择上传方式
      if (this.needChunkUpload) {
        this.startChunkUpload()
      } else {
        this.startNormalUpload()
      }
    },

    /** 普通上传 */
    startNormalUpload() {
      const formData = new FormData()
      formData.append('file', this.selectedFile)

      this.uploading = true
      this.$modal.loading("正在上传文件，请稍候...")

      uploadFile(formData).then(response => {
        this.handleUploadSuccess(response)
      }).catch(error => {
        this.handleUploadError(error)
      })
    },

    /** 分片上传 */
    async startChunkUpload() {
      if (!this.fileMd5) {
        this.$message.warning('文件MD5计算中，请稍后再试')
        return
      }

      try {
        this.uploading = true
        this.showProgress = true
        this.$modal.loading("正在准备分片上传，请稍候...")

        // 1. 初始化分片上传
        const initResult = await initMultipartUpload({
          originalFilename: this.selectedFile.name,
          contentType: this.selectedFile.type || 'application/octet-stream',
          fileTotalSize: this.selectedFile.size,
          customPartSize: this.chunkSizeBytes
        })
        this.uploadId = initResult.data.uploadId
        this.totalChunks = initResult.data.expectedPartCount

        // 2. 检查已上传分片（断点续传）
        const partsResult = await listUploadedParts({uploadId: this.uploadId})
        const uploadedParts = partsResult.data || {}
        this.uploadedChunks = Object.keys(uploadedParts).map(key => parseInt(key))

        // 更新进度
        if (this.totalChunks > 0) {
          this.uploadPercentage = Math.round((this.uploadedChunks.length / this.totalChunks) * 100)
        }

        // 3. 上传所有未上传的分片
        await this.uploadAllChunks()

        // 4. 完成分片合并
        const completeResult = await completeMultipartUpload({
          uploadId: this.uploadId,
          partCount: this.totalChunks,
          originalFilename: this.selectedFile.name,
          fileTotalSize: this.selectedFile.size,
          fileTotalMd5: this.fileMd5
        })

        this.$modal.closeLoading()
        this.$emit('upload-success', completeResult.data)

        setTimeout(() => {
          this.dialogVisible = false
        }, 1000)

      } catch (error) {
        this.handleUploadError(error)
        // 上传失败时尝试清理
        if (this.uploadId) {
          try {
            await abortMultipartUpload({uploadId: this.uploadId})
          } catch (e) {
            console.error('清理分片失败', e)
          }
        }
      }
    },

    /** 上传所有未上传的分片 */
    async uploadAllChunks() {
      if (!this.uploadId || this.totalChunks <= 0) return

      // 计算需要上传的分片
      const chunksToUpload = []
      for (let i = 1; i <= this.totalChunks; i++) {
        if (!this.uploadedChunks.includes(i)) {
          chunksToUpload.push(i)
        }
      }

      if (chunksToUpload.length === 0) {
        return // 所有分片已上传
      }

      // 并发上传控制（同时上传3个分片）
      const concurrency = 3
      const promises = []

      for (let i = 0; i < chunksToUpload.length; i++) {
        const chunkIndex = chunksToUpload[i]
        const promise = this.uploadSingleChunk(chunkIndex)

        promises.push(promise)

        // 控制并发数
        if ((i + 1) % concurrency === 0 || i === chunksToUpload.length - 1) {
          await Promise.all(promises)
          promises.length = 0 // 清空已完成的promise
        }
      }
    },

    /** 上传单个分片 - 使用统一API处理 */
    async uploadSingleChunk(chunkIndex) {
      try {
        // 1. 读取分片数据
        const start = (chunkIndex - 1) * this.chunkSizeBytes
        const end = Math.min(start + this.chunkSizeBytes, this.selectedFile.size)
        const chunkBlob = this.selectedFile.slice(start, end)

        // 2. 创建FormData对象，用于传递分片数据
        const formData = new FormData()
        formData.append('uploadId', this.uploadId)
        formData.append('partNumber', chunkIndex)
        formData.append('partFile', chunkBlob, `chunk-${chunkIndex}`)
        formData.append('partMd5', this.chunkMd5Map[chunkIndex] || '')

        // 3. 调用统一API上传分片（替换原有的fetch调用）
        await uploadPartByServer(formData)

        // 4. 更新进度
        this.uploadedChunks.push(chunkIndex)
        this.uploadPercentage = Math.round((this.uploadedChunks.length / this.totalChunks) * 100)

      } catch (error) {
        console.error(`上传分片 ${chunkIndex} 失败`, error)
        throw new Error(`分片 ${chunkIndex} 上传失败: ${error.msg || error.message || '未知错误'}`)
      }
    },

    /** 计算文件MD5和分片MD5（用于断点续传和文件校验） */
    calculateFileAndChunkMd5(file) {
      this.$modal.loading("正在计算文件MD5，请稍候...")

      return new Promise((resolve, reject) => {
        const fileReader = new FileReader()
        const sparkFile = new SparkMD5.ArrayBuffer() // 用于计算整个文件的MD5
        const chunkSize = this.chunkSizeBytes // 使用配置的分片大小
        let offset = 0
        let currentChunkIndex = 1

        // 读取文件片段
        const loadNextChunk = () => {
          const blob = file.slice(offset, offset + chunkSize)
          // 计算当前分片的MD5
          const sparkChunk = new SparkMD5.ArrayBuffer()
          const chunkReader = new FileReader()

          chunkReader.onload = (e) => {
            // 更新整个文件的MD5
            sparkFile.append(e.target.result)
            // 计算当前分片的MD5
            sparkChunk.append(e.target.result)
            this.chunkMd5Map[currentChunkIndex] = sparkChunk.end()

            offset += chunkSize
            currentChunkIndex++

            // 计算进度（仅显示MD5计算进度）
            const progress = Math.min((offset / file.size) * 100, 100)
            this.uploadPercentage = Math.round(progress)
            this.showProgress = true

            if (offset < file.size) {
              loadNextChunk()
            } else {
              // 计算完成
              this.fileMd5 = sparkFile.end()
              this.$modal.closeLoading()
              resolve(this.fileMd5)
            }
          }

          chunkReader.onerror = (error) => {
            this.$modal.closeLoading()
            this.$message.error('文件MD5计算失败，请重新选择文件')
            console.error('MD5计算错误', error)
            reject(error)
          }

          chunkReader.readAsArrayBuffer(blob)
        }

        // 开始读取
        loadNextChunk()
      })
    },

    /** 上传成功处理 */
    handleUploadSuccess(response) {
      this.uploading = false
      this.$modal.closeLoading()
      this.$modal.msgSuccess("文件上传成功")
      this.$emit('upload-success', response.data)

      setTimeout(() => {
        this.dialogVisible = false
      }, 1000)
    },

    /** 上传失败处理 */
    handleUploadError(error) {
      this.uploading = false
      this.$modal.closeLoading()
      this.$modal.msgError(`文件上传失败: ${error.msg || error.message || '未知错误'}`)
      this.$emit('upload-error', error)
    },

    /** 关闭对话框时重置状态 */
    handleDialogClose() {
      this.resetUploadState()

      // 如果正在上传，尝试取消
      if (this.uploading && this.uploadId) {
        abortMultipartUpload({uploadId: this.uploadId}).catch(e => {
          console.error('取消分片上传失败', e)
        })
      }
    },

    /** 重置上传状态 */
    resetUploadState() {
      this.resetFileSelection()
      this.uploading = false
    }
  }
}
</script>

<style scoped>
.minio-upload {
  display: inline-block;
  margin-right: 10px;
}

.custom-upload-tip {
  margin-top: 8px;
  padding: 0 10px;
  color: #606266;
  font-size: 12px;
  line-height: 1.4;
}

.required-star {
  color: #f56c6c;
  font-weight: 1000;
  margin-right: 2px;
}

/* 上传触发按钮：改造为类似avatar-uploader的样式 */
.upload-trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 120px;
  padding: 8px 16px;
  border: 1px dashed #d9d9d9;
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.3s ease;
  background-color: #fff;
}

</style>
